#ifndef SESSION_H
#define SESSION_H

#include <random>
#include <mutex>

#include <memory>
#include <map>
#include <string>

#include <cassert>

//#include <boost/date_time/posix_time/posix_time.hpp>

//#include <boost/asio.hpp>

#include "sqlite3.h"

#include "Util.h"

namespace scgi
{

class Server;

struct Session
{
	typedef std::shared_ptr<Session> pointer;
	typedef std::map<std::string, std::string> table_type;
	typedef table_type::const_iterator const_iterator;

	unsigned long long sid;
	Time expires;

	table_type table;

	std::string get(std::string key_) const;
	void set(std::string key_, std::string value_);
	
	const_iterator begin() const;
	const_iterator end() const;

	template <typename T_>
	void assign(T_ first_, T_ last_)
	{
		Tokenizer<'=', '&'> tokenize;
		tokenize(
			  first_
			, last_
			, [&](std::string key_, std::string value_)
			{
				URLDecoder decode;
				this->set(
					  decode(key_.begin(), key_.end())
					, decode(value_.begin(), value_.end()));
			});
	}

	std::string to_string();
	std::string get_sid_string() const;
};

class SessionTable
{
private:
	Server * server_ptr;

	sqlite3 * db;
	sqlite3_stmt
		  * stmt_create
		, * stmt_select
		, * stmt_update
		, * stmt_remove_by_expires
		, * stmt_remove_by_sid;

	std::mutex mutex;

	std::mt19937_64 generator;

	size_t try_count;

	CacheT<unsigned long long, Session::pointer> cache;
	//std::deque<unsigned long long> index;

public:
	SessionTable(size_t try_count_ = 10, size_t cache_size_ = 1000);
	~SessionTable();
	
	size_t get_cache_size();

	Session::pointer create(Time expires_);
	int remove(unsigned long long sid_);
	Session::pointer get(unsigned long long sid_);
	int set(Session::pointer session_);
	
	int open_database(std::string path_);
	int create_table();

	int initialize(Server * server_ptr_, unsigned long seed_);
	void tick(int tick_count_);

private:
	Session::pointer create_record(Time expires_);
	int remove_record_by_expires(Time time_);
	int remove_record_by_sid(unsigned long long sid_);
	Session::pointer select_record(unsigned long long sid_);
	int update_record(Session::pointer session_);
};

} // scgi

#endif // SESSION_H
